package com.tanchengjin.admin.exception;

import com.tanchengjin.util.ServerResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.*;
import java.util.stream.Collectors;

@ControllerAdvice
public class GlobalExceptionHandler {
    @Autowired
    private HttpServletRequest request;

    private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = ApplicationException.class)
    @ResponseBody
    public Object applicationExceptionHandler(ApplicationException e, Model model) {
        logger.error(e.getMessage());
        //ajax请求
        if (this.isAjax()) {
            return ServerResponse.responseWithFailureMessage(e.getMessage());
        } else {
            model.addAttribute("message", e.getMessage());
            return "/error/404";
        }
    }

    /**
     * 捕获Validation 校验方法抛出的异常
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public Object methodArgumentNotValidHandler(MethodArgumentNotValidException e, Model model) {
        logger.error(e.getMessage());
        logger.error("methodArgumentNotValid");

        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        HashMap<String, Object> stringObjectHashMap = new HashMap<>();
        String errorMessage = allErrors.size() >= 1 ? allErrors.get(0).getDefaultMessage() : "";
        allErrors.stream().forEach(error -> {
            FieldError e1 = (FieldError) error;
            stringObjectHashMap.put(e1.getField(), e1.getDefaultMessage());
        });

        //ajax请求
        if (this.isAjax()) {
            return ServerResponse.responseWithFailure(1, errorMessage, stringObjectHashMap);
        } else {
            model.addAttribute("message", e.getMessage());
            return "/error/404";
        }
    }

    /**
     * 捕获Validation 校验方法抛出的异常
     */
    @ExceptionHandler(value = {BindException.class})
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public Object bindExceptionHandler(BindException e, Model model) {
        logger.error(e.getMessage());
        logger.error("bindingException");

        //ajax请求
        if (this.isAjax()) {
            return ServerResponse.responseWithFailureMessage(this.getFirstValidateError(e));
        } else {
            model.addAttribute("message", e.getMessage());
            return "/error/404";
        }
    }

    /**
     * requestParam(require=false)方法参数校验
     * 控制器必须使用@Validated注解
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseBody
    public Object ConstraintViolationExceptionHandler(ConstraintViolationException ex, Model model) {

        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));

        //ajax请求
        if (this.isAjax()) {
            return ServerResponse.responseWithFailureMessage(message);
        } else {
            model.addAttribute("message", message);
            return "/error/404";
        }
    }


    /**
     * 未知处理异常
     */
    @ExceptionHandler(value = {Exception.class})
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Object ExceptionHandler(Exception e, Model model) {
        logger.error("Exception:---", e);
        //ajax请求
        if (this.isAjax()) {
            return ServerResponse.responseWithFailureMessage(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
        } else {
            model.addAttribute("message", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
            return "/error/404";
        }
    }


    /**
     * 权限错误异常
     */
    @ExceptionHandler(value = {NeedPermissionException.class})
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    @ResponseBody
    public Object needPermissionExceptionHandler(Exception e, Model model) {
        logger.error("Exception:---", e);
        if (this.isAjax()) {
            return ServerResponse.responseWithFailureMessage(e.getMessage());
        } else {
            model.addAttribute("message", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
            return "/error/404";
        }
    }


    /**
     * 判断是否为ajax请求
     */
    private boolean isAjax() {
        String accept = request.getHeader("accept");
        String xRequestedWith = request.getHeader("X-Requested-With");
        String contentType = request.getContentType();
        return (accept != null && accept.contains("application/json")) ||
                (xRequestedWith != null && xRequestedWith.equals("XMLHttpRequest")) ||
                ("application/json".equals(contentType));
    }

    private List<Map<String, String>> getValidateErrors(BindException exception) {
        ArrayList<Map<String, String>> r = new ArrayList<>();
        exception.getAllErrors().forEach(e -> {
            HashMap<String, String> errorInfo = new HashMap<>();
            FieldError e1 = (FieldError) e;
            errorInfo.put(e1.getField(), e1.getDefaultMessage());
            r.add(errorInfo);
        });
        return r;
    }

    private String getFirstValidateError(BindException exception) {
        List<ObjectError> allErrors = exception.getAllErrors();
        if (!allErrors.isEmpty()) {
            FieldError fieldError = (FieldError) allErrors.get(0);
            return fieldError.getDefaultMessage();
        }
        return "validate error";
    }
}
